namespace Angelus.Data.Linq.OracleClient
{
	using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common;
    using System.Data.OracleClient;
    using System.Data.DLinq.ProviderBase;
    using IsolationLevel=System.Data.IsolationLevel;

    /// <summary>
    /// 
    /// </summary>
    internal class OracleConnectionManager : IConnectionManager, IConnectionUser
    {
        #region Fields

        private readonly OracleConnection _connection;
        private readonly OracleContext _context;
        private readonly OracleInfoMessageEventHandler _infoMessageHandler;
        private readonly int _maxUsers;
        private readonly List<IConnectionUser> _users;
        private bool _autoClose;
        private System.Transactions.Transaction _sysTransaction;
        private OracleTransaction _transaction;

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="connection"></param>
        /// <param name="maxUsers"></param>
        internal OracleConnectionManager(OracleContext context, OracleConnection connection, int maxUsers)
        {
            this._context = context;
            this._connection = connection;
            this._maxUsers = maxUsers;
            this._infoMessageHandler = this.OnInfoMessage;
            this._users = new List<IConnectionUser>(maxUsers);
        }

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        internal bool AutoClose
        {
            get { return this._autoClose; }
            set { this._autoClose = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        internal IDbConnection Connection
        {
            get { return this._connection; }
        }

        /// <summary>
        /// 
        /// </summary>
        internal IDbTransaction LocalTransaction
        {
            get { return this._transaction; }
            set
            {
                if (value != this._transaction)
                {
                    if (null != this._sysTransaction)
                    {
                        throw new Exception("Cannot changed location transaction while a system transaction is in use.");
                    }
                    if ((null != value) && (value.Connection != this._connection))
                    {
                        throw new Exception("Transaction does not match connection");
                    }
                    this._transaction = (OracleTransaction) value;
                }
            }
        }

        #endregion

        #region Private Implementation

        /// <summary>
        /// 
        /// </summary>
        private void AddInfoMessageHandler()
        {
            this._connection.InfoMessage += this._infoMessageHandler;
        }

        /// <summary>
        /// 
        /// </summary>
        private void Enlist()
        {
            System.Transactions.TransactionCompletedEventHandler handler = null;
            if ((null == this._sysTransaction) && (null == this._transaction))
            {
                System.Transactions.Transaction transaction = System.Transactions.Transaction.Current;
                if ((null != transaction) && (System.Transactions.TransactionStatus.Active == transaction.TransactionInformation.Status))
                {
                    this._sysTransaction = transaction;
                    if (!this.IsAutoEnlisted() &&
                        (!this._context.LocalTransactionsOnly || !new LocalTxShim(this).TryEnlistSinglePhase()))
                    {
                        this._connection.EnlistTransaction(this._sysTransaction);
                    }
                    if (null == handler)
                    {
                        handler = delegate
                                      {
                                          try
                                          {
                                              // DO NOT USE DISTRIBUTED TRANSACTION
                                              // this._context.RaiseTransactionCompleted(args);
                                              // DO NOT USE DISTRIBUTED TRANSACTION
                                          }
                                          finally
                                          {
                                              this._sysTransaction = null;
                                              this.ReleaseConnection(this);
                                          }
                                      };
                    }
                    transaction.TransactionCompleted += handler;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private bool IsAutoEnlisted()
        {
            DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
            builder.ConnectionString = this._connection.ConnectionString;
            if (builder.ContainsKey("enlist"))
            {
                return (0 != string.Compare(builder["enlist"].ToString(), "false", StringComparison.OrdinalIgnoreCase));
            }
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void OnInfoMessage(object sender, OracleInfoMessageEventArgs args)
        {
            if (null != this._context.Log)
            {
                this._context.Log.WriteLine("{0}: {1}", args.Source, args.Message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void RemoveInfoMessageHandler()
        {
            this._connection.InfoMessage -= this._infoMessageHandler;
        }

        #endregion

        #region IConnectionManager Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        public void ReleaseConnection(IConnectionUser user)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
            int index = this._users.IndexOf(user);
            if (index >= 0)
            {
                this._users.RemoveAt(index);
            }
            if (((0 == this._users.Count) && this._autoClose) &&
                ((null == this._transaction) && (null == this._sysTransaction)))
            {
                if (ConnectionState.Closed != this._connection.State)
                {
                    this._connection.Close();
                }
                this.RemoveInfoMessageHandler();
                this._autoClose = false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public IDbConnection UseConnection(IConnectionUser user)
        {
            if (null == user)
            {
                throw new ArgumentNullException("user");
            }
            if (ConnectionState.Closed == this._connection.State)
            {
                this._connection.Open();
                this._autoClose = true;
                this.AddInfoMessageHandler();
            }
            if (this._maxUsers == this._users.Count)
            {
                IConnectionUser dummy = this._users[0];
                this._users.RemoveAt(0);
                bool savedAutoClose = this._autoClose;
                this._autoClose = false;
                dummy.CompleteUse();
                this._autoClose = savedAutoClose;
            }
            this._users.Add(user);
            this.Enlist();
            return this._connection;
        }

        #endregion

        #region IConnectionUser Members

        /// <summary>
        /// 
        /// </summary>
        public void CompleteUse()
        {
            // Nothing to do
        }

        #endregion

        #region Nested classes

        /// <summary>
        /// 
        /// </summary>
        private class LocalTxShim : System.Transactions.IPromotableSinglePhaseNotification
        {
            #region Fields

            private readonly OracleConnectionManager _manager;

            #endregion

            #region Constructors

            /// <summary>
            /// 
            /// </summary>
            /// <param name="mgr"></param>
            internal LocalTxShim(OracleConnectionManager mgr)
            {
                this._manager = mgr;
            }

            #endregion

            #region Private Implementation

            /// <summary>
            /// 
            /// </summary>
            /// <param name="isolation"></param>
            /// <returns></returns>
            private static IsolationLevel Convert(System.Transactions.IsolationLevel isolation)
            {
                switch (isolation)
                {
                    case System.Transactions.IsolationLevel.Serializable:
                        return IsolationLevel.Serializable;
                    case System.Transactions.IsolationLevel.RepeatableRead:
                        return IsolationLevel.RepeatableRead;
                    case System.Transactions.IsolationLevel.ReadCommitted:
                        return IsolationLevel.ReadCommitted;
                    case System.Transactions.IsolationLevel.ReadUncommitted:
                        return IsolationLevel.ReadUncommitted;
                    case System.Transactions.IsolationLevel.Snapshot:
                        return IsolationLevel.Snapshot;
                    case System.Transactions.IsolationLevel.Chaos:
                        return IsolationLevel.Chaos;
                }
                return IsolationLevel.Unspecified;
            }

            /// <summary>
            ///
            /// </summary>
            /// <returns></returns>
            internal bool TryEnlistSinglePhase()
            {
                return System.Transactions.Transaction.Current.EnlistPromotableSinglePhase(this);
            }

            #endregion

            #region IPromotableSinglePhaseNotification Members

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            byte[] System.Transactions.ITransactionPromoter.Promote()
            {
                throw new System.Transactions.TransactionPromotionException("Distributed transactions are not allowed.");
            }

            /// <summary>
            /// 
            /// </summary>
            void System.Transactions.IPromotableSinglePhaseNotification.Initialize()
            {
                this._manager._sysTransaction = System.Transactions.Transaction.Current;
                IsolationLevel isolation = Convert(this._manager._sysTransaction.IsolationLevel);
                if (null != this._manager._context.Log)
                {
                    this._manager._context.Log.WriteLine(string.Format("Start Local Transaction ({0})", isolation));
                }
                this._manager._transaction = IsolationLevel.Unspecified == isolation ? this._manager._connection.BeginTransaction() : this._manager._connection.BeginTransaction(isolation);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="singlePhaseEnlistment"></param>
            void System.Transactions.IPromotableSinglePhaseNotification.Rollback(System.Transactions.SinglePhaseEnlistment singlePhaseEnlistment)
            {
                if (null != this._manager._transaction)
                {
                    if (null != this._manager._context.Log)
                    {
                        this._manager._context.Log.WriteLine("Rollback Local Transaction");
                    }
                    this._manager._transaction.Rollback();
                    this._manager._transaction.Dispose();
                    this._manager._transaction = null;
                }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="singlePhaseEnlistment"></param>
            void System.Transactions.IPromotableSinglePhaseNotification.SinglePhaseCommit(System.Transactions.SinglePhaseEnlistment singlePhaseEnlistment)
            {
                try
                {
                    if (null != this._manager._context.Log)
                    {
                        this._manager._context.Log.WriteLine("Commit Local Transaction");
                    }
                    this._manager._transaction.Commit();
                    this._manager._transaction.Dispose();
                    this._manager._transaction = null;
                }
                catch (Exception)
                {
                    singlePhaseEnlistment.Aborted();
                    this._manager._transaction.Dispose();
                    this._manager._transaction = null;
                    return;
                }
                singlePhaseEnlistment.Committed();
            }

            #endregion
        }

        #endregion
    }
}